import type { Attrs, NodeType, Schema } from "./schema.ts";
import type { Mark } from "./mark.ts";
import { Fragment } from "./fragment.ts";
import { type Draft, immerable } from "immer";
/**
 * 基础节点定义 任何数据都可以认为是节点
 */
export class Node {
  [immerable] = true;
  id: string;
  type: NodeType;
  attrs: Attrs;
  content: Fragment;
  marks: Mark[];
  index?: number;
  constructor(
    id: string,
    type: NodeType,
    attrs: Attrs,
    fragment: Fragment,
    marks: Mark[],
    index?: number,
  ) {
    this.id = id;
    this.type = type;
    this.attrs = attrs;
    this.content = fragment;
    this.marks = marks;
    this.index = index;
  }

  nodeAt(id: string): Node | null {
    if (this.id == id) return this;
    return this.content.nodeAt(id) || null;
  }
  forEach(
    callbackfn: (value: Node, index: number, array: Node[]) => void | boolean,
  ) {
    const result = callbackfn(this, 0, [this]);
    if (result !== false) {
      this.content.forEach(callbackfn);
    }
  }
  get childCount(): number {
    return this.content.childCount;
  }

  toJSON(): any {
    let obj: any = { type: this.type.name };
    for (let _ in this.attrs) {
      obj.attrs = this.attrs;
      break;
    }
    if (this.content.childCount) {
      obj.content = this.content.toJSON();
    }
    if (this.marks.length) {
      obj.marks = this.marks.map((n) => n.toJSON());
    }
    return obj;
  }
  toString(): string {
    if (this.type.spec.toDebugString) return this.type.spec.toDebugString(this);
    let name = this.type.name;
    if (this.content.childCount) {
      name += "(" + this.content.toStringInner() + ")";
    }
    return wrapMarks(this.marks, name);
  }
  static setAttrs(node: Draft<Node>, id: string, attrs: Attrs) {
    if (node.id == id) {
      node.attrs = Object.assign({}, node.attrs, attrs);
    } else {
      node.content.forEach((child: any) => {
        Node.setAttrs(child, id, attrs);
      });
    }
  }
  static removeMark(
    node: Draft<Node>,
    id: string,
    filter: (mark: Mark) => boolean,
  ) {
    if (node.id == id) {
      node.marks = node.marks.filter((mark) => !filter(mark));
    } else {
      node.content.forEach((child: any) => {
        Node.removeMark(child, id, filter);
      });
    }
  }
  static addMark(node: Draft<Node>, id: string, mark: Mark) {
    if (node.id == id) {
      node.marks.push(mark);
    } else {
      node.content.content.forEach((child: any) => {
        Node.addMark(child, id, mark);
      });
    }
  }
  static fromJSON(schema: Schema, json: any): Node {
    if (!json) throw new RangeError("Invalid input for Node.fromJSON");
    let marks = null;
    if (json.marks) {
      if (!Array.isArray(json.marks)) {
        throw new RangeError("Invalid mark data for Node.fromJSON");
      }
      marks = json.marks.map(schema.markFromJSON);
    }

    let content = Fragment.fromJSON(schema, json.content);
    return schema.nodeType(json.type).create(json.attrs, content, marks);
  }
}

function wrapMarks(marks: readonly Mark[], str: string) {
  for (let i = marks.length - 1; i >= 0; i--) {
    str = marks[i].type.name + "(" + str + ")";
  }
  return str;
}
